﻿#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008 ZHENG Zhong <heavyzheng nospam-at gmail D0T com>
# - http://heavyz.blogspot.com/
# - http://buggarden.blogspot.com/
#
# Datastore model classes for the forum webapp.
# Created on 2008-05-07.
# $Id: models.py 291 2008-12-03 10:17:53Z heavyzheng $
#

import logging
import cgi
import datetime

from google.appengine.ext import db
from google.appengine.api import users


#---------------------------------------------------------------------------------------------------
# ForumBoard
#---------------------------------------------------------------------------------------------------

class ForumBoard(db.Model):
    """This class represents a forum board.
    """
    
    REQUESTED = "Requested"
    APPROVED  = "Approved"
    DELETED   = "Deleted"
    
    name             = db.StringProperty(required=True, multiline=False)
    desc             = db.StringProperty(multiline=False)
    create_date      = db.DateTimeProperty(auto_now_add=True)
    managers         = db.ListProperty(users.User)
    threads          = db.IntegerProperty(default=0)
    replies          = db.IntegerProperty(default=0)
    last_post_author = db.UserProperty()
    last_post_date   = db.DateTimeProperty()
    status           = db.StringProperty(choices=[REQUESTED, APPROVED, DELETED,], default=REQUESTED)
    
    def posts(self):
        """Returns the number of posts (threads + replies) in this forum board.
        """
        return self.threads + self.replies
    
    def is_current_user_manager(self):
        """Checks if the current user is a manager of this forum board.
        """
        user = users.get_current_user()
        return user is not None and user in self.managers
        
    def put(self):
        is_new = not self.is_saved()
        key = super(ForumBoard, self).put()
        if is_new:
            logging.info( "New ForumBoard '%s' inserted to datastore." % self.name )
        else:
            logging.info( "ForumBoard '%s' updated in datastore." % self.name )
        return key

    def delete(self):
        # Delete the entity from the datastore.
        super(ForumBoard, self).delete()
        logging.info( "ForumBoard '%s' deleted from datastore." % self.name )


#---------------------------------------------------------------------------------------------------
# ForumAttachment
#---------------------------------------------------------------------------------------------------

class ForumAttachment(db.Model):
    """This class represents an attachment on a thread or a reply.
    """

    file_name = db.StringProperty(required=True)
    mime_type = db.StringProperty(required=True)
    data      = db.BlobProperty(required=True)
    size      = db.StringProperty()
    uploader  = db.UserProperty(required=True)

    def is_image(self):
        return self.mime_type.startswith("image/")

    def put(self):
        # Calculate the attachment size.
        bytes = len(self.data)
        if bytes >= 1024 * 1024:
            self.size = "%.1f MB" % (float(bytes) / (1024 * 1024))
        elif bytes >= 1024:
            self.size = "%d KB" % (bytes // 1024)
        else:
            self.size = "%d Bytes" % bytes
        # Save the entity to datastore.
        is_new = not self.is_saved()
        key = super(ForumAttachment, self).put()
        if is_new:
            logging.info( "New ForumAttachment '%s' inserted to datastore." % self.file_name )
        else:
            logging.info( "ForumAttachment '%s' updated in datastore." % self.file_name )
        return key

    def delete(self):
        super(ForumAttachment, self).delete()
        logging.info( "ForumAttachment '%s' deleted from datastore." % self.file_name )


#---------------------------------------------------------------------------------------------------
# ForumThread
#---------------------------------------------------------------------------------------------------

class ForumThread(db.Model):
    """This class represents a forum thread.
    """
    
    board            = db.ReferenceProperty(ForumBoard, required=True)
    title            = db.StringProperty(multiline=False, required=True)
    content          = db.TextProperty()
    attachment       = db.ReferenceProperty(ForumAttachment)
    author           = db.UserProperty(required=True)
    post_date        = db.DateTimeProperty(auto_now_add=True)
    edit_date        = db.DateTimeProperty()
    replies          = db.IntegerProperty(default=0)
    last_post_author = db.UserProperty()
    last_post_date   = db.DateTimeProperty()
    is_locked        = db.BooleanProperty(default=False)
    recommended_till = db.DateTimeProperty()
    
    # This property is used as an order criterion when selecting threads from the datastore. The
    # GQL order-by clause is: "ORDER BY order_as_date DESC": threads with a newer order_as_date
    # have a higher priority when ordering.
    order_as_date    = db.DateTimeProperty()

    def is_current_user_author(self):
        """Checks if the current user is the author of this thread.
        """
        user = users.get_current_user()
        return user is not None and user == self.author
    
    def is_recommended(self):
        """Checks if this thread is recommended.
        """
        return self.recommended_till is not None \
           and self.recommended_till >= datetime.datetime.today()
    
    def put(self):
    
        if self.last_post_author is None:
            self.last_post_author = self.author
        if self.last_post_date is None:
            self.last_post_date = self.post_date
        
        # Set the order_as_date property: recommended threads should have a higher priority when
        # ordering, so set order_as_date to recommended_till; ordinary threads should be ordered
        # by last_post_date, so set order_as_date to last_post_date.
        if self.is_recommended():
            self.order_as_date = self.recommended_till
        else:
            self.order_as_date = self.last_post_date
        # Put this entity to the datastore.
        is_new = not self.is_saved()
        key = super(ForumThread, self).put()
        # If the thread is newly added, notify the owner board.
        if is_new:
            logging.info( "New ForumThread '%s' by %s inserted to datastore."
                        % (self.title, self.author.nickname()) )
            self.board.threads += 1
            self.board.last_post_author = self.author
            self.board.last_post_date   = self.post_date
            self.board.put()
        else:
            logging.info( "ForumThread '%s' by %s updated in datastore."
                        % (self.title, self.author.nickname()) )
        # Return the key of this entity.
        return key
    
    def delete(self):
        # Notify the owner board.
        if self.is_saved():
            self.board.threads -= 1
            if self.board.threads < 0:
                self.board.thread = 0
            self.board.put()
        # Delete the entity from the datastore.
        super(ForumThread, self).delete()
        logging.info( "ForumThread '%s' deleted from datastore." % self.title )

        
#---------------------------------------------------------------------------------------------------
# ForumReply
#---------------------------------------------------------------------------------------------------

class ForumReply(db.Model):
    """This class represents a reply to a certain forum thread.
    """
    
    thread     = db.ReferenceProperty(ForumThread, required=True)
    title      = db.StringProperty()
    content    = db.TextProperty()
    attachment = db.ReferenceProperty(ForumAttachment)
    author     = db.UserProperty(required=True)
    post_date  = db.DateTimeProperty(auto_now_add=True)
    edit_date  = db.DateTimeProperty()
    
    def is_current_user_author(self):
        """Checks if the current user is the author of this reply.
        """
        user = users.get_current_user()
        return user is not None and user == self.author
    
    def put(self):
        # Put the entity to the datastore.
        is_new = not self.is_saved()
        key = super(ForumReply, self).put()
        # If the reply is newly added, notify the owner thread and the owner board.
        if is_new:
            logging.info( "New ForumReply to '%s' by %s inserted to datastore."
                        % (self.thread.title, self.author.nickname()) )
            self.thread.replies += 1
            self.thread.last_post_author = self.author
            self.thread.last_post_date   = self.post_date
            self.thread.put()
            self.thread.board.replies += 1
            self.thread.board.last_post_author = self.author
            self.thread.board.last_post_date   = self.post_date
            self.thread.board.put()
        else:
            logging.info( "ForumReply to '%s' by %s updated in datastore."
                        % (self.thread.title, self.author.nickname()) )
        return key

    def delete(self):
        # Notify the owner thread and the owner board.
        if self.is_saved():
            self.thread.replies -= 1
            if self.thread.replies < 0:
                self.thread.replies = 0
            self.thread.put()
            self.thread.board.replies -= 1
            if self.thread.board.replies < 0:
                self.thread.board.replies = 0
            self.thread.board.put()
        # Delete the entity from the datastore.
        super(ForumReply, self).delete()
        logging.info( "ForumReply to '%s' by %s deleted from datastore."
                    % (self.thread.title, self.author.nickname()) )


